package interceptcrossing;

/**
 * Отрезок. Содержит информацию о двух точках - начале и конце.
 *
 * @author Igor Botyan
 */
public class Intercept {


	private static double INACCURACY = 0.3;

	/**
	 * Начало отрезка. Является левой (для вертикальных отрезков нижней) точкой
	 */
	private Point begin;

	/**
	 * Конец отрезка. Является правой (для вертикальных отрезков верхней) точкой
	 */
	private Point end;

	/**
	 * Создание отрезка по двум точкам. Из них происходит выбор начальной и
	 * конечной точки отрезка
	 *
	 * @param first  первая точка
	 * @param second вторая точка
	 */
	public Intercept(Point first, Point second) {
		if (first.compareTo(second) == 1) {
			this.begin = second;
			this.end = first;
		} else {
			this.begin = first;
			this.end = second;
		}
	}

	/**
	 * Получение начала отрезка
	 *
	 * @return начало отрезка
	 */
	public Point begin() {
		return this.begin;
	}

	/**
	 * Получение конца отрезка
	 *
	 * @return конец отрезка
	 */
	public Point end() {
		return this.end;
	}

	/**
	 * Вычисление длины отрезка
	 *
	 * @return длина отрезка
	 */
	public double length() {
		return this.begin.distanceTo(this.end);
	}

	/**
	 * Является ли отрезок вертикальным
	 *
	 * @return true, если отрезок является вертикальным; иначе false
	 */
	public boolean isVertical() {
		return (this.begin.x() == this.end.x());
	}

	/**
	 * Является ли отрезок горизонтальным
	 *
	 * @return true, если отрезок является горизонтальным; иначе false
	 */
	public boolean isHorizontal() {
		return (this.begin.y() == this.end.y());
	}

	public double getAboveDistance(Intercept intercept, double x) {
		return (intercept.y(x) - this.y(x));
	}

	public double getBelowDistance(Intercept intercept, double x) {
		return (this.y(x) - intercept.y(x));
	}

	/**
	 * Находится ли отрезок выше заданного в заданной абсциссе
	 *
	 * @param intercept отрезок, с положением которого нужно провести сравнение
	 * @param x         абсцисса, в которой нужно провести сравнение
	 * @return true, если отрезок выше заданного в заданной точке; иначе false
	 */
	public boolean isAbove(Intercept intercept, double x) {
		return (1 == this.compareInX(intercept, x));
	}

	/**
	 * Находится ли отрезок ниже заданного в заданной абсциссе
	 *
	 * @param intercept отрезок, с положением которого нужно провести сравнение
	 * @param x         абсцисса, в которой нужно провести сравнение
	 * @return true, если отрезок ниже заданного в заданной точке; иначе false
	 */
	public boolean isBelow(Intercept intercept, double x) {
		return (-1 == this.compareInX(intercept, x));
	}

	/**
	 * Находится ли отрезок левее заданного в заданной ординате
	 *
	 * @param intercept отрезок, с положением которого нужно провести сравнение
	 * @param y         ордината, в которой нужно провести сравнение
	 * @return true, если отрезок левее заданного в заданной точке; иначе false
	 */
	public boolean isLeft(Intercept intercept, double y) {
		return (-1 == this.compareInY(intercept, y));
	}

	/**
	 * Находится ли отрезок правее заданного в заданной ординате
	 *
	 * @param intercept отрезок, с положением которого нужно провести сравнение
	 * @param y         ордината, в которой нужно провести сравнение
	 * @return true, если отрезок правее заданного в заданной точке; иначе false
	 */
	public boolean isRight(Intercept intercept, double y) {
		return (1 == this.compareInY(intercept, y));
	}

	/**
	 * Пересекается ли отрезок с заданным в заданной абсциссе
	 *
	 * @param intercept отрезок, факт пересечения с которым нужно определить
	 * @param x         абсцисса, в которой нужно провести проверку
	 * @return true, если отрезки пересекаются; иначе false
	 */
	public boolean intersectsInX(Intercept intercept, double x) {
		// TODO for vertical (false?)

		return (0 == this.compareInX(intercept, x));
	}

	/**
	 * Пересекается ли отрезок с заданным в заданной ординате
	 *
	 * @param intercept отрезок, факт пересечения с которым нужно определить
	 * @param y         ордината, в которой нужно провести проверку
	 * @return true, если отрезки пересекаются; иначе false
	 */
	public boolean intersectsInY(Intercept intercept, double y) {
		// TODO for horizontal (false ?)

		return (0 == this.compareInY(intercept, y));
	}

	public Point intersects(Intercept intercept) {
		double x0 = this.begin().x();
		double y0 = this.begin().y();
		double x1 = this.end().x();
		double y1 = this.end().y();
		double x2 = intercept.begin().x();
		double y2 = intercept.begin().y();
		double x3 = intercept.end().x();
		double y3 = intercept.end().y();

		double det = (x1 - x0) * (y2 - y3) + (y1 - y0) * (x3 - x2);
		double det1 = (x2 - x0) * (y2 - y3) + (y2 - y0) * (x3 - x2);
		double det2 = (x1 - x0) * (y2 - y0) - (y1 - y0) * (x2 - x0);

		double t = det1 / det;
		double tao = det2 / det;

		if ((t >= 0) && (t <= 1) && (tao >= 0) && (tao <= 1)) {
			double x = x0 + (x1 - x0) * t;
			double y = y0 + (y1 - y0) * t;

			return new Point(x, y);
		}

		return null;
	}


	public int compareInX(Intercept intercept, double x) {
		double y1 = this.y(x);
		double y2 = intercept.y(x);

		if (Math.abs(y1 - y2) < INACCURACY)
			return 0;

		return Double.compare(y1, y2);
	}

	private int compareInY(Intercept intercept, double y) {
		double x1 = this.x(y);
		double x2 = intercept.x(y);

		return Double.compare(x1, x2);
	}

	public double x(double y) {
		if (this.isHorizontal()) {
			return Double.MAX_VALUE;
		}

		return -(this.c() + this.b() * y) / this.a();
	}

	public double y(double x) {
		if (this.isVertical()) {
			return Double.MAX_VALUE;
		}

		double k = (this.end().y() - this.begin().y()) /
			(this.end().x() - this.begin().x());
		double y = this.begin().y() + k * (x - this.begin().x());

		return y;
	}

	/* Ax + By + C = 0 */

	private double a() {
		double y1 = this.begin.y();
		double y2 = this.end.y();

		return (y1 - y2);
	}

	private double b() {
		double x1 = this.begin.x();
		double x2 = this.end.x();

		return (x2 - x1);
	}

	private double c() {
		double x1 = this.begin.x();
		double y1 = this.begin.y();

		return -(this.a() * x1 + this.b() * y1);
	}

	@Override
	public boolean equals(Object obj) {
		boolean ret = false;

		if (obj instanceof Intercept) {
			Intercept intercept = (Intercept) obj;
			ret = (intercept.begin.equals(this.begin)
				&& (intercept.end.equals(this.end)));
		}

		return ret;
	}

	@Override
	public int hashCode() {
		int hash = 3;
		hash = 89 * hash + (this.begin != null ? this.begin.hashCode() : 0);
		hash = 89 * hash + (this.end != null ? this.end.hashCode() : 0);
		return hash;
	}

	@Override
	public String toString() {
		return String.format("[%s ; %s]", this.begin.toString(), this.end.toString());
	}
}
